
package com.gitee.flow.service;

import com.crenjoy.proto.utils.AnyUtil;
import com.gitee.flow.entity.HiTaskinstEntity;
import com.gitee.jmash.common.enums.MimeType;
import com.gitee.jmash.common.lock.DistributedLock;
import com.gitee.jmash.core.jaxrs.ParamsValidationException;
import com.gitee.jmash.core.orm.cdi.JpaTenantService;
import com.gitee.jmash.core.transaction.JakartaTransaction;
import com.gitee.jmash.core.utils.FieldMaskUtil;
import com.gitee.jmash.file.client.cdi.FileClient;
import com.google.protobuf.Any;
import jakarta.enterprise.inject.Typed;
import jakarta.inject.Inject;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import jakarta.transaction.Transactional;
import jakarta.transaction.Transactional.TxType;
import jakarta.validation.Valid;
import jakarta.validation.ValidationException;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.executable.ValidateOnExecution;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.zip.ZipInputStream;
import jmash.flow.protobuf.BusinessStatusReq;
import jmash.flow.protobuf.DeployReq;
import jmash.flow.protobuf.DeployXmlReq;
import jmash.flow.protobuf.HiTaskinstReq;
import jmash.flow.protobuf.ProcInstDeleteReq;
import jmash.flow.protobuf.ProcInstSuspendReq;
import jmash.flow.protobuf.StartProcInstReq;
import jmash.flow.protobuf.TaskAssignReq;
import jmash.flow.protobuf.TaskAttachmentCreateReq;
import jmash.flow.protobuf.TaskAttachmentUpdateReq;
import jmash.flow.protobuf.TaskClaimReq;
import jmash.flow.protobuf.TaskDelegateReq;
import jmash.flow.protobuf.TaskGobackReq;
import jmash.flow.protobuf.TaskHandleReq;
import jmash.flow.protobuf.TaskPriorityReq;
import jmash.flow.protobuf.TaskResolveReq;
import jmash.flow.protobuf.TaskSuspendReq;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.flowable.common.engine.api.FlowableException;
import org.flowable.engine.repository.Deployment;
import org.flowable.engine.runtime.ProcessInstance;
import org.flowable.engine.runtime.ProcessInstanceBuilder;
import org.flowable.engine.task.Attachment;
import org.flowable.task.api.Task;

/**
 * Flow写服务.
 *
 * @author <a href="mailto:service@crenjoy.com">crenjoy</a>
 */
@Typed(FlowWrite.class)
@Transactional(TxType.REQUIRED)
@JpaTenantService
@ValidateOnExecution
public class FlowWriteBean extends FlowReadBean implements FlowWrite, JakartaTransaction {

  private static Log log = LogFactory.getLog(FlowWriteBean.class);

  @Inject
  DistributedLock lock;

  @Override
  @PersistenceContext(unitName = "WriteFlow")
  public void setEntityManager(EntityManager entityManager) {
    this.tem.setEntityManager(entityManager, true);
  }

  @Override
  public Deployment deployFlow(DeployReq req) throws IOException {
    // 1.检查是否重复请求.
    if (!lock.lock(req.getRequestId(), 60)) {
      throw new ParamsValidationException("requestId", "客户端发起重复请求");
    }
    // 2.业务处理.
    String fileExt = FilenameUtils.getExtension(req.getResFile()).toLowerCase();
    MimeType fileType = MimeType.convert(fileExt);

    File file = FileClient.downloadFile(req.getResFile());
    if (!file.exists()) {
      log.error("找不到文件：" + req.getResFile());
      return null;
    }

    FileInputStream inputStream = new FileInputStream(file);
    Deployment deployment = null;
    switch (fileType) {
      case M_zip:
        ZipInputStream zipInputStream = new ZipInputStream(inputStream);
        deployment = repositoryService.createDeployment().tenantId(getTenant())
            .addZipInputStream(zipInputStream).deploy();
        break;
      case M_xml:
      case M_bpmn:
      default:
        String resourceName = getResourceName(req.getName(), req.getRequestId());
        deployment = repositoryService.createDeployment().tenantId(getTenant())
            .addInputStream(resourceName, inputStream).deploy();
    }
    return deployment;
  }

  @Override
  public Deployment deployFlowXml(DeployXmlReq req) {
    // 1.检查是否重复请求.
    if (!lock.lock(req.getRequestId(), 60)) {
      throw new ParamsValidationException("requestId", "客户端发起重复请求");
    }
    String resourceName = getResourceName(req.getName(), req.getRequestId());
    // 2.业务处理.
    return repositoryService.createDeployment().tenantId(getTenant())
        .addString(resourceName, req.getXml()).deploy();
  }

  private String getResourceName(String name, String requestId) {
    String resourceName = name;
    if (StringUtils.isNotBlank(name) && !name.endsWith(".bpmn20.xml") && !name.endsWith(".bpmn")) {
      throw new ParamsValidationException("name", "名称必须以.bpmn 或 .bpmn20.xml结尾");
    } else {
      resourceName = requestId + ".bpmn";
    }
    return resourceName;
  }

  @Override
  public void deleteDeployment(String deploymentId) {
    repositoryService.deleteDeployment(deploymentId);
  }

  @Override
  public ProcessInstance startProcInst(StartProcInstReq req) {
    // 1.检查是否重复请求.
    if (!lock.lock(req.getRequestId(), 60)) {
      throw new ParamsValidationException("requestId", "客户端发起重复请求");
    }
    // 设置登录用户.
    setAuthenticatedUserId();

    ProcessInstanceBuilder builder = runtimeService.createProcessInstanceBuilder();
    builder.processDefinitionKey(req.getProcDefKey());
    builder.businessKey(req.getBusinessKey());
    builder.tenantId(req.getTenant());
    builder.businessStatus(req.getBusinessStatus());

    if (StringUtils.isNotEmpty(req.getProcInstName())) {
      builder.name(req.getProcInstName());
    }

    for (Map.Entry<String, Any> entry : req.getVarsMap().entrySet()) {
      builder.variable(entry.getKey(), AnyUtil.unpack(entry.getValue()));
    }

    ProcessInstance instance = builder.start();
    return instance;
  }

  @Override
  public int claimTask(TaskClaimReq req) {
    // 1.检查是否重复请求.
    if (!lock.lock(req.getRequestId(), 60)) {
      throw new ParamsValidationException("requestId", "客户端发起重复请求");
    }
    String userId = setAuthenticatedUserId();
    int i = 0;
    for (String taskId : req.getTaskIdList()) {
      boolean success = claimTask(req, taskId, userId);
      i = success ? i++ : i;
    }
    return i;
  }

  /**
   * 认领任务.
   */
  public boolean claimTask(TaskClaimReq req, String taskId, String userId) {
    try {
      if (req.getClaim()) {
        // 认领
        taskService.claim(taskId, userId);
      } else if (req.getForce()) {
        // 强制撤回认领
        taskService.unclaim(taskId);
      } else {
        // 撤回自己的认领
        Task task = taskService.createTaskQuery().taskTenantId(getTenant()).taskId(taskId)
            .taskClaimedBy(userId).singleResult();
        if (null != task) {
          taskService.unclaim(taskId);
        }
      }
      return true;
    } catch (Exception ex) {
      LogFactory.getLog(FlowWriteBean.class).warn("", ex);
      return false;
    }
  }

  @Override
  public boolean completeTask(TaskHandleReq req) {
    // 1.检查是否重复请求.
    if (!lock.lock(req.getRequestId(), 60)) {
      throw new ParamsValidationException("requestId", "客户端发起重复请求");
    }
    Task task = findTaskById(req.getTaskId());
    if (task == null) {
      return false;
    }
    String userId = setAuthenticatedUserId();
    // 完成当前任务
    Map<String, Object> variables = new HashMap<>();
    for (Map.Entry<String, Any> entry : req.getVarsMap().entrySet()) {
      variables.put(entry.getKey(), AnyUtil.unpack(entry.getValue()));
    }
    if (!variables.containsKey("action") && StringUtils.isNotBlank(req.getAction())) {
      variables.put("action", req.getAction());
    }
    if (!variables.containsKey("comment")) {
      variables.put("comment", req.getComment());
    }
    // 办理记录信息
    addComment(task, req.getComment());
    taskService.complete(req.getTaskId(), userId, variables);
    return false;
  }

  private void addComment(Task task, String comment) {
    taskService.addComment(task.getId(), task.getProcessInstanceId(), comment);
  }

  @Override
  public boolean gobackTask(TaskGobackReq req) {
    // 1.检查是否重复请求.
    if (!lock.lock(req.getRequestId(), 60)) {
      throw new ParamsValidationException("requestId", "客户端发起重复请求");
    }
    Task currentTask = findTaskById(req.getTaskId());
    if (currentTask == null) {
      throw new FlowableException("Current task not found.");
    }

    // 获取历史任务实例数.
    long count = historyService.createHistoricTaskInstanceQuery()
        .processInstanceId(currentTask.getProcessInstanceId())
        .taskDefinitionKey(req.getTaskDefKey()).finished().count();
    if (count == 0) {
      throw new FlowableException("退回的用户任务" + req.getTaskDefKey() + "未找到.");
    }

    setAuthenticatedUserId();
    // 退回理由信息
    String comment =
        StringUtils.isBlank(req.getComment()) ? "退回" : "退回理由:" + req.getComment();
    addComment(currentTask, comment);

    // 使用变更活动状态功能跳转到目标节点
    runtimeService.createChangeActivityStateBuilder()
        .processInstanceId(currentTask.getProcessInstanceId())
        .moveActivityIdTo(currentTask.getTaskDefinitionKey(), req.getTaskDefKey()).changeState();

    return true;
  }


  @Override
  public Attachment insertAttachment(TaskAttachmentCreateReq attachment) {
    // 1.检查是否重复请求.
    if (!lock.lock(attachment.getRequestId(), 60)) {
      throw new ParamsValidationException("requestId", "客户端发起重复请求");
    }
    Task task = this.findTaskById(attachment.getTaskId());
    if (task == null) {
      throw new ParamsValidationException("taskId", "任务ID不能为空，或找不到该任务");
    }
    setAuthenticatedUserId();
    return taskService.createAttachment(attachment.getType(), task.getId(),
        task.getProcessInstanceId(), attachment.getName(), attachment.getDescription(),
        attachment.getUrl());
  }

  @Override
  public Attachment updateAttachment(TaskAttachmentUpdateReq req) {
    Attachment entity = taskService.getAttachment(req.getAttachmentId());
    if (null == entity) {
      throw new ValidationException("找不到实体:" + req.getAttachmentId());
    }
    // 无需更新,返回当前数据库数据.
    if (req.getUpdateMask().getPathsCount() == 0) {
      return entity;
    }
    // 更新掩码属性
    FieldMaskUtil.copyMask(entity, req, req.getUpdateMask());
    // 1.业务校验.
    Task task = this.findTaskById(req.getTaskId());
    if (task == null) {
      throw new ParamsValidationException("taskId", "任务ID不能为空，或找不到该任务");
    }
    if (!StringUtils.equals(task.getProcessInstanceId(), entity.getProcessInstanceId())) {
      throw new ParamsValidationException("taskId", "任务与流程实例不一致");
    }
    // 2.仅校验,不执行.
    if (req.getValidateOnly()) {
      return entity;
    }

    // 3.检查是否重复请求.
    if (!lock.lock(req.getRequestId(), 60)) {
      throw new ParamsValidationException("requestId", "客户端发起重复请求");
    }

    // 4.执行业务
    setAuthenticatedUserId();
    taskService.saveAttachment(entity);
    return entity;
  }

  @Override
  public Attachment deleteAttachment(String attachmentId) {
    Attachment entity = taskService.getAttachment(attachmentId);
    if (null == entity) {
      throw new ValidationException("找不到实体:" + attachmentId);
    }
    setAuthenticatedUserId();
    taskService.deleteAttachment(attachmentId);
    return entity;
  }

  @Override
  public boolean suspendProcInst(ProcInstSuspendReq req) {
    // 1.检查是否重复请求.
    if (!lock.lock(req.getRequestId(), 60)) {
      throw new ParamsValidationException("requestId", "客户端发起重复请求");
    }
    setAuthenticatedUserId();
    if (req.getSuspend()) {
      runtimeService.suspendProcessInstanceById(req.getProcInstId());
    } else {
      runtimeService.activateProcessInstanceById(req.getProcInstId());
    }
    return true;
  }

  @Override
  public boolean deleteProcInst(ProcInstDeleteReq req) {
    // 1.检查是否重复请求.
    if (!lock.lock(req.getRequestId(), 60)) {
      throw new ParamsValidationException("requestId", "客户端发起重复请求");
    }
    // 查询流程是否办理过.
    HiTaskinstReq.Builder hiTaskinstReq = HiTaskinstReq.newBuilder();
    hiTaskinstReq.setTenant(getTenant());
    hiTaskinstReq.setProcInstId(req.getProcInstId());
    hiTaskinstReq.setState("completed");
    List<HiTaskinstEntity> list = hiTaskinstDao.findListByReq(hiTaskinstReq.build());
    if (list.size() > 0) {
      throw new ValidationException("流程已办理，不能删除");
    }
    runtimeService.deleteProcessInstance(req.getProcInstId(), req.getDeleteReason());
    return true;
  }

  @Override
  public boolean suspendTask(TaskSuspendReq req) {
    // 1.检查是否重复请求.
    if (!lock.lock(req.getRequestId(), 60)) {
      throw new ParamsValidationException("requestId", "客户端发起重复请求");
    }
    String userId = setAuthenticatedUserId();
    if (req.getSuspend()) {
      taskService.suspendTask(req.getTaskId(), userId);
    } else {
      taskService.activateTask(req.getTaskId(), userId);
    }
    return true;
  }

  @Override
  public boolean assignTask(TaskAssignReq req) {
    // 1.检查是否重复请求.
    if (!lock.lock(req.getRequestId(), 60)) {
      throw new ParamsValidationException("requestId", "客户端发起重复请求");
    }
    Task currentTask = findTaskById(req.getTaskId());
    if (currentTask == null) {
      throw new FlowableException("Current task not found.");
    }
    String userId = setAuthenticatedUserId();
    // 分配办理用户.
    if (StringUtils.isNotBlank(req.getUserId())) {
      taskService.setAssignee(req.getTaskId(), userId);
    }
    // 分配候选用户.
    if (req.getCandidateUserCount() > 0) {
      for (String candidateUser : req.getCandidateUserList()) {
        taskService.addCandidateUser(req.getTaskId(), candidateUser);
      }
    }
    // 记录分配、移交备注.
    addComment(currentTask, req.getComment());
    return true;
  }

  @Override
  public boolean priorityTask(TaskPriorityReq req) {
    // 1.检查是否重复请求.
    if (!lock.lock(req.getRequestId(), 60)) {
      throw new ParamsValidationException("requestId", "客户端发起重复请求");
    }
    setAuthenticatedUserId();
    // 默认Task优先级50.
    taskService.setPriority(req.getTaskId(), req.getPriorityValue() * 50);
    return true;
  }

  @Override
  public boolean delegateTask(TaskDelegateReq req) {
    // 1.检查是否重复请求.
    if (!lock.lock(req.getRequestId(), 60)) {
      throw new ParamsValidationException("requestId", "客户端发起重复请求");
    }
    Task currentTask = findTaskById(req.getTaskId());
    if (currentTask == null) {
      throw new FlowableException("Current task not found.");
    }
    setAuthenticatedUserId();
    // 记录委托备注
    addComment(currentTask, req.getComment());
    taskService.delegateTask(req.getTaskId(), req.getUserId());
    return true;
  }

  @Override
  public boolean resolveTask(TaskResolveReq req) {
    // 1.检查是否重复请求.
    if (!lock.lock(req.getRequestId(), 60)) {
      throw new ParamsValidationException("requestId", "客户端发起重复请求");
    }
    Task currentTask = findTaskById(req.getTaskId());
    if (currentTask == null) {
      throw new FlowableException("Current task not found.");
    }
    setAuthenticatedUserId();
    // 完成委托办理.
    Map<String, Object> variables = new HashMap<>();
    for (Map.Entry<String, Any> entry : req.getVarsMap().entrySet()) {
      variables.put(entry.getKey(), AnyUtil.unpack(entry.getValue()));
    }
    if (!variables.containsKey("action") && StringUtils.isNotBlank(req.getAction())) {
      variables.put("action", req.getAction());
    }
    taskService.resolveTask(req.getTaskId(), variables);
    // 记录完成委托备注
    addComment(currentTask, req.getComment());
    return true;
  }

  @Override
  public boolean setBusinessStatus(String procInstId, String businessStatus) {
    runtimeService.updateBusinessStatus(procInstId, businessStatus);
    return true;
  }


}
